package com.lida.study.sudo;

import com.lida.study.common.utils.sudo.SudoUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author lD
 * @date 2019/7/59:50
 */
public class SudoRun {

    public SudoRun() {}

    /**
     * 等待计算的时间，默认是10秒
     */
    private int timeOut = 10000;

    public int getTimeOut() {
        return timeOut;
    }

    public void setTimeOut(int timeOut) {
        this.timeOut = timeOut;
    }

    /**
     * 尝试次数
     */
    private volatile AtomicInteger tryCount = new AtomicInteger(0);

    /**
     * 正在运行的线程数
     */
    private volatile AtomicInteger runningThread = new AtomicInteger(0);

    /**
     * 尝试次数加一
     */
    private void tryTimesIncrement(){
        tryCount.incrementAndGet();
        runningThread.incrementAndGet();
    }

    /**
     * 尝试次数减一
     */
    private void tryTimesDecrement(){
        runningThread.decrementAndGet();
    }

    /**
     * 所有的答案集合
     */
    private List<Sudo> answers = new ArrayList<>();

    /**
     * 线程池
     */
    private ThreadPoolExecutor pool;

    /**
     * 线程是否在没有任务时关闭
     */
    private boolean threadPoolShutdown = true;

    private long begin = System.currentTimeMillis();

    /**
     * 初始化线程池，并启动线程池运行完毕检测辅助线程
     */
    private void poolInit() {
        if (pool == null || pool.isShutdown()) {
            // 初始化线程池
            pool = new ThreadPoolExecutor(
                    // 核心线程数
                    100,
                    // 最大线程数
                    500,
                    // 闲置线程存活时间
                    1,
                    // 存活时间单位
                    TimeUnit.SECONDS,
                    /* 线程队列
                     * SynchronousQueue：直接提交。当新任务到达时，将立即将任务交给线程执行，不存在队列中，先创建核心线程，
                     *                      当核心线程达到corePoolSize后，再创建临时线程，临时线程数达到maxinumPoolSize后，新来的任务交由handler处理
                     * LinkedBlockingQueue：线程池中，最多只会有corePoolSize个线程在运行，其他任务将存在于队列中等待，此时maximumPoolSize参数无效
                     * ArrayBlockingQueue：如果线程数还没达到corePoolSize，则新建核心线程执行，否则将任务添加到队列中等待，
                     *                      队列满了后将创建临时线程，当临时线程达到maximumPoolSize时，新来的任务交由handler处理
                     */
                    new ArrayBlockingQueue<>(200),
                    // 线程工厂
                    Executors.defaultThreadFactory(),
                    // 队列已满,而且当前线程数已经超过最	大线程数时的异常处理策略
                    new ThreadPoolExecutor.AbortPolicy()
            );
            // 设置线程池核心线程闲置超时时自动关闭
            pool.allowCoreThreadTimeOut(true);
            // 启动辅助线程，检查程序是否结束
            new Thread(() -> {
                try {
                    while (true) {
                        Thread.sleep(100);
                        // 当核心线程为0时关闭线程池
                        if (runningThread.get() == 0) {
                            if (threadPoolShutdown) {
                                pool.shutdown();
                                System.out.println("threadPool closed successfully");
                            }
                            System.out.println("procedure use time: " + (System.currentTimeMillis() - begin));
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 中断线程
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }

    public void setThreadPoolShutdown(boolean threadPoolShutdown) {
        this.threadPoolShutdown = threadPoolShutdown;
    }

    public List<Sudo> run(String s) {
        if (StringUtils.isEmpty(s)) {
            return new ArrayList<>();
        }
        // 计算数独维度
        if (s.length() == 81) {
            SudoUtils.setSize(9);
        } else if (s.length() == 256) {
            SudoUtils.setSize(16);
        } else {
            return new ArrayList<>();
        }
        try {
            return run(SudoUtils.asSudo(s));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    public List<Sudo> run(Sudo s) {
        if (s == null) {
            return new ArrayList<>();
        }
        begin(s);
        return getAnswers();
    }

    /**
     * 当计算出所有结果后，返回结果集
     * @return
     */
    private List<Sudo> getAnswers() {
        // 根据超时时间计算最大检测次数
        int maxTimes = timeOut / 50;
        int tryTimes = 0;
        while(true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 中断线程
                Thread.currentThread().interrupt();
            }
            if (runningThread.get() == 0) {
                return answers;
            }
            if (tryTimes > maxTimes) {
                return answers;
            }
            tryTimes++;
        }
    }

    /**
     * 开始解数独
     * @param s
     */
    private void begin(Sudo s) {
        // 初始化后检查数独正确性

        Map.Entry<Boolean, String> check = SudoUtils.check(s);
        if (!check.getKey()) {
            // 数独不正确
            return;
        }
        while (true) {
            int count = s.getCount();
            if (SudoUtils.exclude(s) == null) {
                // 没有解开
                return;
            }
            if (SudoUtils.isFinished(s)) {
                Map.Entry<Boolean, String> check2 = SudoUtils.check(s);
                if (!check2.getKey()) {
                    // 数独不正确
                    return;
                }
                System.out.println(
                        "After " + tryCount.get() + " tries, it found the answer in: " + (System.currentTimeMillis() - begin) + " millisecond(s):");
                System.out.println(s.toString());
                answers.add(s);
                return;
            }
            // 经过一番处理，返现数据没有改变，则尝试设置假定值
            if (count == s.getCount()) {
                // 需要启动计划B，生成可能的实例
                if (!SudoUtils.createChildren(s)) {
                    System.out.println("launch plan B is faild!");
                    return;
                } else {
                    for (Sudo child : s.getChildren()) {
                        // 启用线程池来加快速度
                        poolInit();
                        pool.execute(() -> {
                            run(child);
                            tryTimesDecrement();
                        });
                        tryTimesIncrement();
                    }
                    return;
                }
            }
        }
    }
}
